<template>
  <div ref="app" class="app">
    <div class="top-form">
      <el-button style="margin-left: 20px" type="primary" @click="submit"
        >确定并保存</el-button
      >
    </div>

    <div class="container_warp">
      <div class="btn-box">
        <div class="components">
          <span>组件面板</span>
        </div>
        <div
          class="components-btn"
          v-for="(item, index) in List"
          :key="index"
          :title="item"
          @mousedown="startDrag(item, $event)"
        >
          <span>{{ item.comType }}</span>
        </div>
      </div>
      <div id="containerChart"></div>
    </div>

    <!-- <el-button type="primary" @click="toInfo">初始</el-button> -->
    <data-source
      :data="componentData"
      :open="dataSourceOpen"
      @onClose="dataSourceOpen = false"
      @getData="setCellData"
    />
    <serve-system
      :data="componentData"
      :open="serveSystemOpen"
      @onClose="serveSystemOpen = false"
      @getData="setCellData"
    />
    <filter-module
      :data="componentData"
      :open="filterModuleOpen"
      @onClose="filterModuleOpen = false"
      @getData="setCellData"
    />
    <desensitization
      :data="componentData"
      :open="desensitizationOpen"
      @onClose="desensitizationOpen = false"
      @getData="setCellData"
    />
    <transformer
      :data="componentData"
      :open="transformerOpen"
      @onClose="transformerOpen = false"
      @getData="setCellData"
    />
    <map-system
      :data="componentData"
      :open="mapSystemOpen"
      @onClose="mapSystemOpen = false"
      @getData="setCellData"
    />
  </div>
</template>
<script setup name="processes">
import "@antv/x6-vue-shape/lib";
import { Graph, Shape, FunctionExt } from "@antv/x6/dist/x6.js";
import { ElMessage } from 'element-plus'
import { startDragToGraph } from './methods.js';
import dataSource from './components/dataSource.vue';
import serveSystem from './components/serveSystem.vue';
import filterModule from './components/filterModule.vue';
import desensitization from './components/desensitization.vue';
import transformer from './components/transformer.vue';
import mapSystem from './components/mapSystem.vue';
import { onMounted, toRefs, reactive } from 'vue';

const data = reactive({
  componentData: {},
  grid: {
    // 网格设置
    size: 10, // 网格大小 10px
    visible: true, // 渲染网格背景
    type: 'mesh',
    args: {
      color: '#D0D0D0',
      thickness: 1, // 网格线宽度/网格点大小
      factor: 10,
    },
  },
});
const { componentData, grid } = toRefs(data);
const dataSourceOpen = ref(false);
const serveSystemOpen = ref(false);
const filterModuleOpen = ref(false);
const desensitizationOpen = ref(false);
const transformerOpen = ref(false);
const mapSystemOpen = ref(false);
let cellData = ref([]);
let app = ref(null);
let curCell = ref(null);
let graph = ref(null);
let selectCell = ref(null);
const List = ref([
  {
    comType: '开始',
    comHType: 'START',
  },
  {
    comType: '结束',
    comHType: 'END',
  },
  {
    comType: '数源组件',
    comHType: 'DATA_SOURCE',
  },
  {
    comType: '服务组件',
    comHType: 'SERVICE',
  },
  {
    comType: '过滤组件',
    comHType: 'FILTER',
  },
  {
    comType: '脱敏组件',
    comHType: 'DESENSITIZATION',
  },
  {
    comType: '变压组件',
    comHType: 'TRANSFORMER',
  },
  {
    comType: '并流组件',
    comHType: 'CONCURRENT',
  },
  {
    comType: '转换组件',
    comHType: 'CALCULATOR',
  }
]);

// 初始化流程图画布
const initX6 = () => {
  graph = new Graph({
    container: document.getElementById('containerChart'),
    width: app.value.offsetWidth-180,
    height: 768,
    grid: grid.value,
    mousewheel: {
      enabled: true,
      zoomAtMousePosition: true,
      modifiers: 'ctrl',
      minScale: 0.5,
      maxScale: 3,
    },
    panning: {
      enabled: true,
      modifiers: 'shift',
    },
    connecting: {
      router: {
        name: 'manhattan',
        args: {
          padding: 1,
        },
      },
      connector: {
        name: 'rounded',
        args: {
          radius: 8,
        },
      },
      anchor: 'center',
      connectionPoint: 'anchor',
      allowBlank: false,
      snap: {
        radius: 20,
      },
      createEdge() {
        return new Shape.Edge({
          attrs: {
            line: {
              stroke: '#A2B1C3',
              strokeWidth: 2,
              targetMarker: {
                name: 'block',
                width: 12,
                height: 8,
              },
            },
          },
          zIndex: 0,
        });
      },
      validateConnection({ targetMagnet }) {
        return !!targetMagnet;
      },
    },
    highlighting: {
      magnetAdsorbed: {
        name: 'stroke',
        args: {
          attrs: {
            fill: '#5F95FF',
            stroke: '#5F95FF',
          },
        },
      },
    },
    resizing: true,
    rotating: true,
    selecting: {
      enabled: true,
      rubberband: true,
      showNodeSelectionBox: true,
    },
    snapline: true,
    keyboard: true,
    clipboard: true,
    history: true,
  });
  //delete
  graph.bindKey('delete', () => {
    const cells = graph.getSelectedCells();
    if (cells.length) {
      graph.removeCells(cells);
    }
  });
  // 回退
  graph.bindKey('ctrl+z', () => {
    graph.history.undo();
  });
  // 前进
  graph.bindKey('ctrl+Y', () => {
    graph.history.redo();
  });
  // 鼠标移入移出节点
  graph.on(
    'node:mouseenter',
    FunctionExt.debounce(() => {
      const container = document.getElementById('containerChart');
      const ports = container.querySelectorAll('.x6-port-body');
      showPorts(ports, true);
    }),
    500,
  );
  // 双击弹窗
  graph.on('cell:dblclick', ({ e, x, y, cell, view }) => {
    curCell = cell;
    showComponent();
  });
  graph.on('node:mouseleave', () => {
    const container = document.getElementById('containerChart');
    const ports = container.querySelectorAll('.x6-port-body');
    showPorts(ports, false);
  });

  graph.on('selection:changed', (args) => {
    args.added.forEach((cell) => {
      selectCell = cell;
      if (cell.isEdge()) {
        cell.isEdge() && cell.attr('line/strokeDasharray', 5); //虚线蚂蚁线
        cell.addTools([
          {
            name: 'vertices',
            args: {
              padding: 4,
              attrs: {
                strokeWidth: 0.1,
                stroke: '#2d8cf0',
                fill: '#ffffff',
              },
            },
          },
        ]);
      }
    });
    args.removed.forEach((cell) => {
      cell.isEdge() && cell.attr('line/strokeDasharray', 0); //正常线
      cell.removeTools();
    });
  });
};
// 是否显示连接桩
const showPorts = (ports, show) => {
  for (let i = 0, len = ports.length; i < len; i = i + 1) {
    ports[i].style.visibility = show ? 'visible' : 'hidden';
  }
};
// 数据初始化
const toInfo = () => {
  cellData.value = [];
  graph.fromJSON(cellData.value);
};
// 拖拽生成正方形
const startDrag = (type, e) => {
  startDragToGraph(graph, type, e);
};
// 双击组件判断
const showComponent = () => {
  let comType = curCell.data.comType;
  componentData.value = curCell.data;
  if (comType == '数源组件') {
    dataSourceOpen.value = true;
  } else if (comType == '服务组件') {
    serveSystemOpen.value = true;
  } else if (comType == '过滤组件') {
    filterModuleOpen.value = true;
  } else if (comType == '脱敏组件') {
    desensitizationOpen.value = true;
  } else if (comType == '变压组件') {
    transformerOpen.value = true;
  } else if (comType == '转换组件') {
    mapSystemOpen.value = true;
  }
};
// 修改组件数据
const setCellData = (e) => {
  curCell.setData(e);
  curCell.setAttrs({
    label: { text: e.comName ? e.comName : e.comType },
    body: { stroke: '#a0a0a0' },
  });
};
// 保存并提交
const submit = () => {
  if (cellVerify()) {
    console.log(JSON.stringify(this.cellData));
  }
};
// 流程图验证
const cellVerify = () => {
  cellData.value = graph.toJSON().cells;
  let startArr = cellData.value.filter(
    (J) => J.shape == 'rect' && J.data.comType == '开始',
  );
  let endArr = cellData.value.filter(
    (J) => J.shape == 'rect' && J.data.comType == '结束',
  );
  // 判断是否包含开始结束组件
  if (startArr.length == 1 && endArr.length == 1) {
    // 1，开始组件没有入口、只有出口
    // 2，结束组件只有入口、没有出口
    let startId = startArr[0].id;
    let endId = endArr[0].id;
    let Root = graph.getRootNodes(); // '根节点'
    let isStart = Root.length == 1 && Root[0].id == startId;
    let isend = graph.getLeafNodes().some((J) => J.id == endId); //this.graph.getLeafNodes() '叶子点'
    // 3.开始组件出口对应组件必须是数源组件
    let startNodeArr = graph.getNeighbors(graph.getCellById(startId));
    let isStartNode = startNodeArr.every((J) => J.data.comType == '数源组件');

    // 4，其它组件可以有一个或者多个入口
    // 5，并流组件可以有一个或者多个出口，其它组件都只有一个出口
    let nodeArr = cellData.value.filter(
      (J) =>
        J.shape == 'rect' &&
        J.data.comType != '开始' &&
        J.data.comType != '结束' &&
        J.data.comType != '并流组件',
    );
    let isNode = true;
    nodeArr.forEach((item) => {
      let childNodeArr = graph.getNeighbors(
        graph.getCellById(item.id),
        { outgoing: true },
      );
      if (childNodeArr.length > 1) {
        isNode = false;
      }
    });
    // 6.结束组件入口对应组件必须是服务组件
    let endNodeArr = graph.getNeighbors(graph.getCellById(endId), {
      incoming: true,
    });
    let isEndNode = endNodeArr.every((J) => J.data.comType == '服务组件');
    if (!isStart) {
      ElMessage({
        message: '流程图只能包含一个入口，并且为开始组件',
        type: 'error',
      });
    } else if (!isStartNode) {
      ElMessage({
        message: '开始组件必须连接数源组件',
        type: 'error',
      });
    } else if (!isEndNode) {
      ElMessage({
        message: '结束组件的入口必须是服务组件',
        type: 'error',
      });
    } else if (!isNode) {
      ElMessage({
        message: '除开始、结束、并流组件外只能含有一个出口',
        type: 'error',
      });
    } else if (!isend) {
      ElMessage({
        message: '流程图的出口必须包含结束组件',
        type: 'error',
      });
    } else {
      // 7.所有组件信息填写正确
      let isAllverify = true;
      nodeArr.forEach((item) => {
        if (!item.data.comState) {
          graph.getCellById(item.id).setAttrs({
            body: { stroke: 'red' },
          });
          isAllverify = false;
        }
      });
      if (isAllverify) {
        return true;
      } else {
        ElMessage({
          message: '组件信息未补全',
          type: 'error',
        });
        return false;
      }
    }
  } else {
    ElMessage({
      message: '开始和结束组件有且仅有一个',
      type: 'error',
    });
    return false;
  }
};
onMounted(() => {
  initX6();
  toInfo();
});
</script>

<style lang="scss" scoped>
.app {
  // background: #fff;
}
.top-form {
  width: 100%;
  display: flex;
  justify-content: center;
  padding: 10px;
}
.container_warp {
  display: flex;
  justify-content: start;
}
#containerChart {
  width: 1000px;
  height: 600px;
  border: 1px solid #dfe3e8;
  float: left;
}
.btn-box {
  width: 180px;
  padding-bottom: 20px;
  .components {
    padding: 10px;
    text-align: center;
    background-color: #409eff;
    margin-bottom: 20px;
    color: #fff;
  }
  & > .components-btn {
    margin: 10px auto;
    width: 100px;
    height: 40px;
    cursor: pointer;
    background: #fff;
    border: 1px solid #dcdfe6;
    color: #606266;
    display: block;
    font-size: 14px;
    border-radius: 4px;
    text-align: center;
    line-height: 40px;
  }
  & > .components-btn:hover {
    color: #409eff;
    border-color: #c6e2ff;
    background-color: #ecf5ff;
  }
}
.bottom {
  margin: 20px;
  width: 100%;
  display: flex;
  justify-content: center;
}
::v-deep {
  .my-form {
    .el-form-item {
      margin: 0 !important;
    }
  }
}
</style>
